/*
 * Copyright 2004 original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jbfilter.jsp.fcomps;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspTag;

import org.apache.commons.lang.StringUtils;
import org.jbfilter.core.CleaningStrategy;
import org.jbfilter.core.EnableStrategy;
import org.jbfilter.core.FilterComponent;
import org.jbfilter.core.FilterComponentContainer;
import org.jbfilter.core.NullTestingEnableable;
import org.jbfilter.core.factory.FilterComponentProvider;
import org.jbfilter.core.factory.FilterComponentProviderRegistry;
import org.jbfilter.jsp.FilterComponentContainerTag;
import org.jbfilter.jsp.FilterTagCallable;
import org.jbfilter.jsp.JbfilterSimpleTagSupport;
import org.jbfilter.jsp.JbfilterTag;
import org.jbfilter.jsp.JbfilterTagAttribute;
import org.jbfilter.jsp.PropertyPathHandler;
import org.jbfilter.jsp.ProviderSupport;

public abstract class FilterComponentTag<E> extends JbfilterSimpleTagSupport {

	private FilterComponent<E> filterComponent;
	private String id; // mandatory
	private Boolean enabled;
	private Boolean inverted;// to inverse filter component logic if set and true
	private String var;// puts filter component in page for easier retrieval
	private PropertyPathHandler propertyPathHandler = null;
	
	private CleaningStrategy cleaningStrategy;// rarely used, for maximal flexibility
	private EnableStrategy enableStrategy;// rarely used, for maximal flexibility
	private boolean cleaningStrategySet;
	private boolean enableStrategySet;
	private ProviderSupport providerSupport = new ProviderSupport();
	
	private Boolean nullTestingEnabled;
	public static final String BLANK = "_blank";
	
	@Override
	public void doTag() throws JspException, IOException {
		super.doTag();
		
		FilterComponentContainerTag<E> parent = retrieveParent();
		// if provider info is not set on tag, lookup the info on the parent
		String providerName = getProviderName();
		String providerClassName = getProviderClassName();
		if (StringUtils.isBlank(providerName) && StringUtils.isBlank(providerClassName)) {
			setProviderName(parent.getProviderName());
			setProviderClassName(parent.getProviderClassName());
		}
		FilterComponentContainer<E> fcompContainer = (FilterComponentContainer<E>)parent.getFilterComponentContainer();
		filterComponent = retrieveComponent(fcompContainer);
		setProperties();
		if (var != null) {
			setPageScopeAttribute(var, filterComponent);
		}
		if (this instanceof FilterTagCallable) {
			parent.registerFilterTagCallable((FilterTagCallable)this);
		}
	}

	/**
	 * Setting the components properties passed in by the user by setting the tag's attributes.
	 */
	private void setProperties() throws JspException {
		if (inverted != null) {
			filterComponent.setInverted(inverted);	
		}
		if (enabled != null) {
			filterComponent.setEnabled(enabled);	
		}
		if(cleaningStrategySet) {
			filterComponent.setCleaningStrategy(cleaningStrategy);
		}
		if(enableStrategySet) {
			filterComponent.setEnableStrategy(enableStrategy);
		}
		if(nullTestingEnabled != null && (filterComponent instanceof NullTestingEnableable)) {
			((NullTestingEnableable)filterComponent).setNullTestingEnabled(nullTestingEnabled);
		}
		if (propertyPathHandler != null) {
			propertyPathHandler.handlePropertyPath(getFilterComponent());
		}
		setPropertiesFilterComponentTagCallback();
	}

	abstract protected void setPropertiesFilterComponentTagCallback() throws JspException;
	
	/**
	 * If the component exists : retrieves it from the filter, otherwise creates new one.
	 * @return
	 * @throws JspException 
	 */
	private FilterComponent<E> retrieveComponent(FilterComponentContainer<E> fcompContainer) throws JspException {
		FilterComponent<E> fc = fcompContainer.getFilterComponent(id);
		if (fc != null) {
			return fc;
		}
		FilterComponent<E> fcomp = createComponent();
		fcomp.setId(id);
		// the enableStrategy decides whether the component should be enabled or not 
		// according to the components state
		EnableStrategy enStr = enableStrategy;
		if (enStr == null) {
			enStr = getJspEnableStrategy();
		}
		fcomp.setEnableStrategy(enStr);
		fcompContainer.addFilterComponent(fcomp);
		return fcomp;
	}
	
	/**
	 * Creates the component.
	 * @return the newly created component
	 */
	abstract protected FilterComponent<E> createComponent() throws JspException;
	
	
	protected FilterComponentProvider getFilterComponentProvider() throws JspException {
		String providerName = getProviderName();
		String providerClassName = getProviderClassName();
		if (StringUtils.isNotBlank(providerName)) {
			return FilterComponentProviderRegistry.getInstance().fromName(providerName);
		} else if (StringUtils.isNotBlank(providerClassName)) {
			return FilterComponentProviderRegistry.getInstance().fromClassName(providerClassName);
		} else {
			throw new JspException(String.format("%s or %s must be set in tag %s or in its' ancestor tags.",
					JbfilterTagAttribute.providerName,
					JbfilterTagAttribute.providerClassName,
					getJbfilterTag()));
		}
	}
	abstract protected JbfilterTag getJbfilterTag();
	/**
	 * A default strategy set on creation only, with reasonable JSP behavior.
	 * In most cases a strategy that disables for 'blank' values.
	 * @return
	 */
	abstract protected EnableStrategy getJspEnableStrategy();

	/**
	 * Retrieves the parent tag which should be of type {@link FilterComponentContainerTag}.
	 * @return
	 * @throws JspException
	 */
	protected FilterComponentContainerTag<E> retrieveParent() throws JspException {
		JspTag parent = getParent();
		if (parent == null || !(parent instanceof FilterComponentContainerTag)) {
			throw new JspException(getClass() + " must have as parent : " + FilterComponentContainerTag.class);
		}
		@SuppressWarnings("unchecked")
		FilterComponentContainerTag<E> retour =(FilterComponentContainerTag<E>)parent;
		return retour;
	}
	/**
	 * Tests if the filter component can test for {@code null} values.
	 * @return
	 */
	protected boolean isNullTestingCase() {
		if( !(filterComponent instanceof NullTestingEnableable) ) {
			return false;
		}
		if (((NullTestingEnableable)filterComponent).isNullTestingEnabled()) {
			return true;
		} else {
			return false;
		}
	}
	public void setId(String id) {
		this.id = id;
	}
	public void setEnabled(Boolean enabled) {
		this.enabled = enabled;
	}
	public void setInverted(Boolean inverted) {
		this.inverted = inverted;
	}
	public void setVar(String var) {
		this.var = var;
	}
	public void setCleaningStrategy(CleaningStrategy cleaningStrategy) {
		this.cleaningStrategy = cleaningStrategy;
		cleaningStrategySet = true;
	}
	public void setEnableStrategy(EnableStrategy enableStrategy) {
		this.enableStrategy = enableStrategy;
		enableStrategySet = true;
	}
	protected FilterComponent<E> getFilterComponent() {
		return filterComponent;
	}
	public void setProviderName(String providerName) {
		providerSupport.setProviderName(providerName);
	}
	public void setProviderClassName(String providerClassName) {
		providerSupport.setProviderClassName(providerClassName);
	}
	protected String getProviderName() {
		return providerSupport.getProviderName();
	}
	protected String getProviderClassName() {
		return providerSupport.getProviderClassName();
	}
	public void setNullTestingEnabled(Boolean nullTestingEnabled) {
		this.nullTestingEnabled = nullTestingEnabled;
	}
	public void setPropertyPath(String propertyPath) {
		if (propertyPathHandler == null) {
			propertyPathHandler = new PropertyPathHandler();
		}
		propertyPathHandler.setPropertyPath(propertyPath);
	}
	protected PropertyPathHandler getPropertyPathHandler() {
		return propertyPathHandler;
	}
}
